package com.snail.starter.utils.id;

import cn.hutool.core.util.IdUtil;

import java.security.SecureRandom;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;


public class ObjectId {

    private static final int           LOW_ORDER_THREE_BYTES = 0x00ffffff;
    private static final short         PROC                  = NetUtils.getProcId();
    private static final int           HOST                  = NetUtils.getMachineId();
    private static final AtomicInteger INC                   = new AtomicInteger(new SecureRandom().nextInt());
    private static final char[]        HEX_CHARS             = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    private static final char[] SHORT_CHARS = new char[]{'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
            't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
            'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
            'W', 'X', 'Y', 'Z'};

    /**
     * 短位ID
     *
     * @return
     */
    public static String nextShortId() {
        StringBuilder shortBuffer = new StringBuilder();
        String        uuid        = UUID.randomUUID().toString().replace("-", "");
        for (int i = 0; i < 8; i++) {
            String str = uuid.substring(i * 4, i * 4 + 4);
            int    x   = Integer.parseInt(str, 16);
            shortBuffer.append(SHORT_CHARS[x % 0x3E]);
        }
        return shortBuffer.toString();
    }

    public static void resetId() {
        if (INC.get() >= Integer.MAX_VALUE) {
            INC.set(0);
        }
    }

    /***
     * 产生固定格式的UUID:[4字节Unix时间戳][3字节机器hash][2字节进程号][3字节自增].toHexString(24)
     *
     * @return UUID
     */
    public static String nextId() {
        int   hid       = HOST;
        short pid       = PROC;
        int   timestamp = (int) (System.currentTimeMillis() / 1000);
        int   counter   = INC.getAndIncrement() & LOW_ORDER_THREE_BYTES;

        byte[] bytes = new byte[12];

        //encode timestamp
        bytes[0] = int3(timestamp);
        bytes[1] = int2(timestamp);
        bytes[2] = int1(timestamp);
        bytes[3] = int0(timestamp);

        //encode hostid
        bytes[4] = int2(hid);
        bytes[5] = int1(hid);
        bytes[6] = int0(hid);

        //encode pid
        bytes[7] = short1(pid);
        bytes[8] = short0(pid);

        //encode ID
        bytes[9] = int2(counter);
        bytes[10] = int1(counter);
        bytes[11] = int0(counter);

        return toHexString(bytes);
    }

    private static byte[] parseHexString(String s) {
        if (!isValid(s)) {
            throw new IllegalArgumentException("invalid  ObjectId: [" + s + "]");
        }
        byte[] b = new byte[12];
        for (int i = 0; i < b.length; i++) {
            b[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
        }
        return b;
    }

    /***
     * 解析objectId里的Unix时间戳
     *
     * @param objId
     * @return
     */
    public static long getTime(String objId) {
        byte[] bs        = parseHexString(objId);
        int    timestamp = makeInt(bs[0], bs[1], bs[2], bs[3]);
        return timestamp * 1000L;
    }

    /**
     * Checks if a string could be an {@code ObjectId}.
     *
     * @param hexString a potential ObjectId as a String.
     * @return whether the string could be an object id
     * @throws IllegalArgumentException if hexString is null
     */
    public static boolean isValid(final String hexString) {
        if (hexString == null) {
            throw new IllegalArgumentException();
        }

        int len = hexString.length();
        if (len != 24) {
            return false;
        }

        for (int i = 0; i < len; i++) {
            char c = hexString.charAt(i);
            if (c >= '0' && c <= '9') {
                continue;
            }
            if (c >= 'a' && c <= 'f') {
                continue;
            }
            if (c >= 'A' && c <= 'F') {
                continue;
            }

            return false;
        }
        return true;
    }

    /**
     * Converts this instance into a 24-byte hexadecimal string representation.
     *
     * @return a string representation of the ObjectId in hexadecimal format
     */
    public static String toHexString(byte[] bs) {
        int    i     = 0;
        char[] chars = new char[24];
        for (byte b : bs) {
            chars[i++] = HEX_CHARS[b >> 4 & 0xF];
            chars[i++] = HEX_CHARS[b & 0xF];
        }
        return new String(chars);
    }

    // Big-Endian helpers, in this class because all other BSON numbers are little-endian
    private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) {
        return (((b3) << 24) | ((b2 & 0xff) << 16) | ((b1 & 0xff) << 8) | ((b0 & 0xff)));
    }

    private static byte int3(final int x) {
        return (byte) (x >> 24);
    }

    private static byte int2(final int x) {
        return (byte) (x >> 16);
    }

    private static byte int1(final int x) {
        return (byte) (x >> 8);
    }

    private static byte int0(final int x) {
        return (byte) (x);
    }

    private static byte short1(final short x) {
        return (byte) (x >> 8);
    }

    private static byte short0(final short x) {
        return (byte) (x);
    }

    public static long nextLongId(){
        return IdUtil.getSnowflake().nextId();
    }
}